package com.glsc.ngateway.platform.service.sequenceproduce;

import com.glsc.ngateway.common.base.domain.mysql.gateway.system.SystemClassConfig;
import com.glsc.ngateway.common.base.exception.PlatformException;
import com.glsc.ngateway.common.base.repo.mysql.gateway.platform.system.SystemClassConfigRepository;
import com.glsc.ngateway.platform.service.sequenceproduce.context.ProjectAddSeqContext;
import com.glsc.ngateway.platform.service.sequenceproduce.context.SystemAddSeqContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.text.SimpleDateFormat;
import java.util.Objects;

/**
 * 发号器
 *
 * @author xuchang
 */
@Component
public class SequenceProducer {

    private static final Integer RETRY_COUNT_MAX = 5;

    private static SystemClassConfigRepository systemClassConfigRepository;

    @Autowired
    public void setSystemClassConfigRepository(SystemClassConfigRepository systemClassConfigRepository) {
        SequenceProducer.systemClassConfigRepository = systemClassConfigRepository;
    }

    /**
     * 根据businessType匹配对应生成规则
     * TODO: 使用充血模型，将各个策略分开
     */
    public static <T extends SequenceProduceContext> void produce(T context) {
        if (context instanceof SystemAddSeqContext) {
            context.setBusinessType(BusinessTypeEnum.IT_SYSTEM);
            produceBy((SystemAddSeqContext) context);
        } else if (context instanceof ProjectAddSeqContext) {
            context.setBusinessType(BusinessTypeEnum.IT_PROJECT);
            produceBy((ProjectAddSeqContext) context);
        }
    }

    private static void produceBy(SystemAddSeqContext context) {
        //自动生成编号
        SystemClassConfig classConfig = systemClassConfigRepository.findByMainClassAndSystemClass(context.getMainClass(), context.getSystemClass());
        classConfig.setClassNoSeq(classConfig.getClassNoSeq() + 1);
        systemClassConfigRepository.save(classConfig);
        context.setRes(context.getSystemClass() + "-" + String.format("%03d", classConfig.getClassNoSeq()));
    }

    /**
     * 原理：1、然后对应业务实现加工，最后生成号码
     * 2、数据库乐观锁， 先select，然后update where type = ？ and num = ？，如果失败了，重新获取
     * 3、重试5次后，抛出异常
     */
    private static void produceBy(ProjectAddSeqContext context) {
        SystemClassConfig classConfig;
        while (true) {
            classConfig = systemClassConfigRepository.findByBusinessType(context.getBusinessType().getCode());
            if (Objects.isNull(classConfig)) {
                //第一次没有记录，新增
                classConfig = new SystemClassConfig();
                classConfig.setBusinessType(context.getBusinessType().getCode());
                classConfig.setDeleted("0");
                classConfig.setClassNoSeq(0);
                systemClassConfigRepository.save(classConfig);
            }
            //乐观锁，如果当时条件记录未改变，则更新记录并，跳出循环
            if (systemClassConfigRepository.existsByBusinessTypeAndClassNoSeq(classConfig.getBusinessType(), classConfig.getClassNoSeq())) {
                //更新记录
                classConfig.setClassNoSeq(classConfig.getClassNoSeq() + 1);
                systemClassConfigRepository.save(classConfig);
                break;
            } else if (context.getRetryCount() < RETRY_COUNT_MAX) {
                //否则重试5次
                context.setRetryCount(context.getRetryCount() + 1);
            } else {
                //5次以上抛异常
                throw PlatformException.error("【发号器】\"项目\"发号失败，重复超过5次");
            }
        }
        //最后结果
        SimpleDateFormat df = new SimpleDateFormat("yyyyMM");
        String date = df.format(System.currentTimeMillis());
        //唯一标识-创建年月-实施主体-四位0-顺序号
        context.setRes(context.getBusinessType().getCode() + "-" + date + "-" + context.getImplmentsSubject() + "-" + String.format("%04d", classConfig.getClassNoSeq() / 10000) + "-" + String.format("%04d", classConfig.getClassNoSeq() % 10000));
    }
}
